#pragma once

#include <iostream>
#include <memory>
#include <unordered_map>
#include "Epoller.hpp"
#include "Connection.hpp"

class Reactor
{
    static const int size = 128;
public: 
    Reactor()
        :_epoller_ptr(std::make_unique<Epoller>()),
         _isRunning(false)
    {}

    ~Reactor(){}

    void loop()
    {
        // 有链接才启动程序
        if(_connections.empty())
            return;

        int timeout = -1;
        _isRunning = true;
        while(true) {
            print();
            int n = loopOnce(timeout); // epoll_wait
            dispather(n);              // 派发事件 
        }
        _isRunning = false;
    }

    void stop() { _isRunning = false; }

    void addConnection(const std::shared_ptr<Connection> &con)
    {
        int fd = con->getSockfd();
        uint32_t event = con->getEvents();

        if(!_connections.count(fd)) {
            // 1. 对该连接进行关心,即添加到epoll模型中
            _epoller_ptr->addEvent(fd, event);
            // 2. 将该连接加入到connections中进行管理
            _connections[fd] = con;
            // 3. 将该连接的回指指针指向该server
            con->setPrivateData(this);
        }
    }

    void delConnection(int sockfd)
    {
        // 1.从epoll模型中取消关心
        _epoller_ptr->delEvent(sockfd);
        // 2.从connection中删除该连接
        _connections.erase(sockfd);
        // 3.关闭该文件描述符
        close(sockfd);
    }

    void enableReadWrite(int sockfd, bool enableR, bool enableW)
    {
        if(_connections.count(sockfd)) {
            uint32_t new_events = (EPOLLET | (enableR ? EPOLLIN : 0) | (enableW ? EPOLLOUT : 0));
            _epoller_ptr->modEvent(sockfd, new_events);
        }
    }
private:
    int loopOnce(int timeout)
    {
        // epoll模型等待文件描述符
        // _revents作为输出型参数，所有就绪得文件描述符以及就绪的事件都在_revents中
        return _epoller_ptr->waitEvent(_revents, size, timeout);
    }

    void dispather(int n)
    {
        for(int i=0; i<n; i++) {
            // 获取一个fd以及对应就绪的事件
            int sockfd = _revents[i].data.fd;
            uint32_t revents = _revents[i].events;

            // 对事件进行分类派发
            // 因为在就绪事件中，除了IO外，还有可能出现文件描述符错误或者是对方关闭文件描述符
            // 也就是说，我们要对这些就绪事件都进行判断，然后进行处理，如果直接使用if判断，就会导致出现多个if语句
            // 我们这里将所有的就绪事件异常，都转换为IO错误
            // 我们让异常就绪事件也或上IO事件，这样它们都会在recv那里出错
            // 我们统一在recv中处理
            if((revents & EPOLLERR) || (revents & EPOLLHUP))
                revents |= (EPOLLIN | EPOLLOUT);

            if(revents & EPOLLIN) {
                // 本次连接可能在异常处理之后不在存在，如果直接进行recv会导致出错，所以在IO前先进行判断
                if(_connections.count(sockfd))
                    _connections[sockfd]->recver(); 
            }
            if(revents & EPOLLOUT) {
                // 本次连接可能在异常处理之后不在存在，如果直接进行recv会导致出错，所以在IO前先进行判断
                if(_connections.count(sockfd))
                    _connections[sockfd]->sender(); 
            }
        }
    }

    void print()
    {
        std::cout << "当前正在被reactor管理的fd: ";
        for(auto & con : _connections)
            std::cout << con.first << " ";
        std::cout << std::endl;
    }
private:
    // 1.epoll模型
    std::unique_ptr<Epoller> _epoller_ptr;
    // 2.服务器运行标志位
    bool _isRunning;
    // 3.管理所有的连接 fd:connection
    std::unordered_map<int, std::shared_ptr<Connection>> _connections;
    // 4.就绪事件
    struct epoll_event _revents[size];
};